/*
 * Course:    CS 590 Web Services
 * Semseter:  Spring 2009
 * Professor: Sedlemeyer
 * Project:   CHEAT (Code Help Educational Assignment Tool)
 * Due Date:  May 1, 2009
 * Team:      Sarcasm
 *            (Baker, Jason; Mitchell, Robin; Shie, Keith)
 */
package cheat.assignment;

import cheat.entities.AssignmentCitation;
import cheat.entities.AssignmentKeyword;
import cheat.entities.AssignmentKeywordType;
import cheat.entities.AssignmentCustomProperty;
import cheat.entities.AssignmentExternalUrl;
import cheat.entities.AssignmentProperty;
import cheat.entities.AssignmentType;
import cheat.entities.Course;
import cheat.entities.IsoLanguage;
import cheat.entities.Person;
import cheat.entities.ProgramLanguageSpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

/**
 * Transform an AssignmentProperty entity graph into an assignment
 * metadata byte array.
 *
 * @author Keith Shie
 */
public class AssignmentBuilder {
  static private Transformer trans;
  private static DocumentBuilder docBld;

  private static final String TARGET_NS = "http://web-cat.org/1.0/assignment-sharing";

  final private byte[] content;


  /**
   * Construct an object from which a byte array representation of the
   * assignment metadata can be extracted.
   *
   * @param assignment entity graph representing an assignment's metadata.
   */
  public AssignmentBuilder( AssignmentProperty assignment ) {

    byte[] result = null;

    try {
      result = createDom( assignment );
    } catch (SAXException ex) {
      Logger.getLogger( AssignmentBuilder.class.getName() ).log( Level.SEVERE, null, ex );
    } catch (MalformedURLException ex) {
      Logger.getLogger( AssignmentBuilder.class.getName() ).log( Level.SEVERE, null, ex );
    } catch (ParserConfigurationException ex) {
      Logger.getLogger( AssignmentBuilder.class.getName() ).log( Level.SEVERE, null, ex );
    } catch (TransformerConfigurationException ex) {
      Logger.getLogger( AssignmentBuilder.class.getName() ).log( Level.SEVERE, null, ex );
    } catch (TransformerException ex) {
      Logger.getLogger( AssignmentBuilder.class.getName() ).log( Level.SEVERE, null, ex );
    } catch (IOException ex) {
      Logger.getLogger( AssignmentBuilder.class.getName() ).log( Level.SEVERE, null, ex );
    }

    content = (result == null) ? new byte[]{} : result;
  }


  static private synchronized  Transformer getTransformer() throws TransformerConfigurationException, TransformerFactoryConfigurationError {
//    dom.Writer dump = new dom.Writer();
//    dump.setOutput( new OutputStreamWriter( System.out ) );
////    dump.setCanonical( true );
//    dump.write( assignment );
//
//    System.out.println("\n");
    if ( trans == null ){
      final TransformerFactory transFac = TransformerFactory.newInstance();
      trans = transFac.newTransformer();
    }

    return trans;
  }


  private byte[] asBytes( final Document aDoc, final DocumentBuilder docBld ) throws TransformerFactoryConfigurationError,
                                                                                     TransformerException, SAXException,
                                                                                     TransformerConfigurationException,
                                                                                     IOException {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    getTransformer().transform( new DOMSource( aDoc ), new StreamResult( baos ) );
    
    // check that the created doc is valid
//    ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() );
//
//    Document validDoc = docBld.parse( bais );
    byte[] result = baos.toByteArray();
    return result;
  }


  final private String asTokenList( String contentTypeCd, Collection<AssignmentKeyword> keywordList ) {
    final StringBuilder sb = new StringBuilder();
    for (AssignmentKeyword k : keywordList) {
      if (contentTypeCd.equals( k.getKeywordCode() )) {
        if (sb.length() > 0) {
          sb.append( ", " );
        }
        sb.append( k.getContentValue() );
      }
    }

    return sb.toString();
  }


  private static synchronized  DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
//    final SchemaFactory schFac = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
//    Schema assignmentSchema = schFac.newSchema( new URL(
//      "http://amethyst.ipfw.edu:8080/cs590-project/assignment-sharing.xsd" ) );
    if ( docBld == null ){
      final DocumentBuilderFactory docBldFac = DocumentBuilderFactory.newInstance();
  //    docBldFac.setSchema( assignmentSchema );
      docBldFac.setNamespaceAware( true );
      docBldFac.setCoalescing( true );
      docBld = docBldFac.newDocumentBuilder();
    }

    return docBld;
  }


  final private byte[] createDom( AssignmentProperty assignment ) throws SAXException, MalformedURLException,
                                                                         ParserConfigurationException,
                                                                         TransformerConfigurationException,
                                                                         TransformerException, IOException {
    final DateFormat dtFmt = new SimpleDateFormat( "yyyy-MM-dd kk:mm:ss z" );

    final String nsUri = TARGET_NS;

    final DocumentBuilder docBld = getDocumentBuilder();
    final Document aDoc = docBld.newDocument();

    aDoc.setXmlVersion( "1.1" );
    aDoc.setDocumentURI( nsUri );
    aDoc.setXmlStandalone( true );

    final Element root = aDoc.createElementNS( nsUri, "assignment" );

    aDoc.appendChild( root );
    root.setAttribute( "xmlns", TARGET_NS );
//    root.setAttributeNS( "xmlns", "xsi",  );
//    root.setAttributeNS( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "schemaLocation",TARGET_NS + " " +
//                  "../CheatApp-common/xml-resources/jaxb/Assignment%20Sharing/amethyst.ipfw.edu_8080/cs590-project/assignment-sharing.xsd");

    final Element title = aDoc.createElementNS( nsUri, "title" );
    title.appendChild( aDoc.createTextNode( assignment.getTitle() ) );
    root.appendChild( title );

    final Element abstrct = aDoc.createElementNS( nsUri, "abstract" );
    abstrct.appendChild( aDoc.createTextNode( assignment.getAbstractTxt() ) );
    root.appendChild( abstrct );

    final ProgramLanguageSpec p = assignment.getProgLangSpec();

    if (p != null) {
      final Element progLang = aDoc.createElementNS( nsUri, "programming-language" );
      progLang.setAttribute( "name", p.getName() );
      if (p.getLangVersion() != null) {
        progLang.setAttribute( "version", p.getLangVersion() );
      }
      if (p.getVendor() != null) {
        progLang.setAttribute( "vendor", p.getVendor() );
      }

      root.appendChild( progLang );
    }

    final Collection<Person> authors = assignment.getPersonCollection();
    for (Person author : authors) {
      final Element authorElem = aDoc.createElementNS( nsUri, "author" );
      root.appendChild( authorElem );

      if (author.getFirstName() != null) {
        final Element first = aDoc.createElementNS( nsUri, "first-name" );
        first.appendChild( aDoc.createTextNode( author.getFirstName() ) );
        authorElem.appendChild( first );
      }

      if (author.getLastName() != null) {
        final Element last = aDoc.createElementNS( nsUri, "last-name" );
        last.appendChild( aDoc.createTextNode( author.getLastName() ) );
        authorElem.appendChild( last );
      }

      if (author.getEmail() != null) {
        final Element email = aDoc.createElementNS( nsUri, "email" );
        email.appendChild( aDoc.createTextNode( author.getEmail() ) );
        authorElem.appendChild( email );
      }

      if (author.getUrl() != null) {
        final Element url = aDoc.createElementNS( nsUri, "url" );
        url.appendChild( aDoc.createTextNode( author.getUrl() ) );
        authorElem.appendChild( url );
      }

      if (author.getAffiliation() != null) {
        final Element affiliation = aDoc.createElementNS( nsUri, "affiliation" );
        affiliation.appendChild( aDoc.createTextNode( author.getAffiliation() ) );
        authorElem.appendChild( affiliation );
      }

      if (author.getPhone() != null) {
        final Element phone = aDoc.createElementNS( nsUri, "phone" );
        phone.appendChild( aDoc.createTextNode( author.getPhone() ) );
        authorElem.appendChild( phone );
      }
    }

    final Element date = aDoc.createElementNS( nsUri, "date" );

    date.setAttribute( "created", dtFmt.format( assignment.getCreateDt() ) );
    date.setAttribute( "modified", dtFmt.format( assignment.getUpdDt() ) );
    root.appendChild( date );

    /* Build Language list */
    final Element languages = aDoc.createElementNS( nsUri, "languages" );
    final IsoLanguage primLang = assignment.getPrimaryLanguageCode();
    final String primLangCode = primLang.getLanguageCode();
    StringBuilder sb = new StringBuilder( primLangCode );
    final Set<String> langSet = new HashSet<String>();

    for (IsoLanguage il : assignment.getIsoLangCollection()) {
      langSet.add( il.getLanguageCode() );
    }
    langSet.remove( primLangCode );

    for (String s : langSet) {
      sb.append( ',' );
      sb.append( s );
    }
    languages.appendChild( aDoc.createTextNode( sb.toString() ) );
    root.appendChild( languages );

    /* License */
    final Element rights = aDoc.createElementNS( nsUri, "rights" );
    root.appendChild( rights );

    final Element copyright = aDoc.createElementNS( nsUri, "copyright" );
    copyright.appendChild( aDoc.createTextNode( assignment.getCopyright() ) );
    rights.appendChild( copyright );

    final Element license = aDoc.createElementNS( nsUri, "license" );
    license.appendChild( aDoc.createTextNode( assignment.getLicenseId().getDescr() ) );
    rights.appendChild( license );

    final Element url = aDoc.createElementNS( nsUri, "url" );
    url.appendChild( aDoc.createTextNode( assignment.getLicenseId().getUrl() ) );
    rights.appendChild( url );

    final Collection<AssignmentKeyword> keywordList = assignment.getKeywordCollection();

    if (keywordList != null && keywordList.size() > 0) {
      final Element keywords = aDoc.createElementNS( nsUri, "keywords" );
      root.appendChild( keywords );

      final Element topics = aDoc.createElementNS( nsUri, "topics" );
      topics.appendChild( aDoc.createTextNode( asTokenList( AssignmentKeywordType.TOPIC_CD, keywordList) ) );
      keywords.appendChild( topics );

      final Element prerequisites = aDoc.createElementNS( nsUri, "prerequisites" );
      prerequisites.appendChild( aDoc.createTextNode( asTokenList( AssignmentKeywordType.PREREQUISITE_CD, keywordList) ) );
      keywords.appendChild( prerequisites );

      final Element other = aDoc.createElementNS( nsUri, "other" );
      other.appendChild( aDoc.createTextNode( asTokenList( AssignmentKeywordType.OTHER_CD, keywordList) ) );
      keywords.appendChild( other );

      final Element course = aDoc.createElementNS( nsUri, "course" );
      final StringBuilder sb1 = new StringBuilder();
      for ( Course c : assignment.getCourseCollection() ){
        if ( sb1.length() > 0 ){
          sb1.append(", ");
        }
        sb1.append(c.getLabel());
        final String d = c.getDescription();
        if ( d != null && d.length() > 0 ){
          sb1.append(": ");
          sb1.append(d);
        }
      }
      course.appendChild( aDoc.createTextNode( sb1.toString() ) );
      keywords.appendChild( course );
    }
    
    final AssignmentType t = assignment.getAssignmentTypeCode();
    if ( t != null ){
      final Element type = aDoc.createElementNS( nsUri, "type" );
      type.appendChild( aDoc.createTextNode( t.getAssignmentTypeCode() ) );
      root.appendChild( type );
    }

    final String buildCmd = assignment.getBuildCommand();
    final String testCmd = assignment.getTestCommand();
    if ( buildCmd != null || testCmd != null ){
      final Element command = aDoc.createElementNS( nsUri, "command" );
      root.appendChild( command );

      final Element build = aDoc.createElementNS( nsUri, "build" );
      build.appendChild( aDoc.createTextNode(buildCmd) );
      command.appendChild( build );

      final Element test = aDoc.createElementNS( nsUri, "test" );
      test.appendChild( aDoc.createTextNode(testCmd) );
      command.appendChild( test );
    }

    final String lObj = assignment.getLearningObjective();
    if ( lObj != null ){
      final Element learningObj = aDoc.createElementNS( nsUri, "learning-objectives" );
      learningObj.appendChild( aDoc.createTextNode(lObj) );
      root.appendChild( learningObj );
    }

    if ( assignment.getPrerequisiteCollection() != null){
      final StringBuilder sb1 = new StringBuilder();

      for ( AssignmentProperty pr : assignment.getPrerequisiteCollection() ){
        if ( sb1.length() > 0 ){
          sb1.append( ",");
        }
        sb1.append( pr.getUuid());
      }
      final Element prereq = aDoc.createElementNS( nsUri, "prerequisite-assignments" );
      prereq.appendChild( aDoc.createTextNode(sb1.toString()) );
      root.appendChild( prereq );
    }

    final Collection<AssignmentExternalUrl> urls = assignment.getAssignmentExternalUrlCollection();
    final Collection<AssignmentCitation> cites = assignment.getAssignmentCitationCollection();
    if ( ( urls != null && urls.size() > 0 ) || ( cites != null && cites.size() > 0 ) ){
      final Element info = aDoc.createElementNS( nsUri, "info" );
      root.appendChild( info );

      for ( AssignmentExternalUrl u : urls ){
        final Element info_url = aDoc.createElementNS( nsUri, "url" );
        info_url.appendChild( aDoc.createTextNode(u.getUrl()) );
        info.appendChild( info_url );
      }

      for ( AssignmentCitation c : cites ){
        final Element info_cite = aDoc.createElementNS( nsUri, "cite" );
        info_cite.appendChild( aDoc.createTextNode(c.getCitation()) );
        info.appendChild( info_cite );
      }
    }

    if ( assignment.getEstDifficulty() != null ||
            assignment.getEstSize() != null ||
            assignment.getEstTime() != null ){
      final Element estimated = aDoc.createElementNS( nsUri, "estimated" );

      if ( assignment.getEstDifficulty() != null ){
        estimated.setAttribute( "difficulty", assignment.getEstDifficulty().toString() );
      }
      if ( assignment.getEstTime() != null ){
        estimated.setAttribute( "time", assignment.getEstTime() );
      }
      if ( assignment.getEstSize() != null ){
        estimated.setAttribute( "size", assignment.getEstSize().toString() );
      }
      root.appendChild( estimated );
    }

    final Element repos = aDoc.createElementNS( nsUri, "repository" );
    repos.setAttribute( "uuid", String.valueOf( assignment.getUuid() ) );
    repos.setAttribute( "version", String.valueOf( assignment.getVersion() ) );
    root.appendChild( repos );

    if ( assignment.getParent() != null ){
      final Element repos_parent = aDoc.createElementNS( nsUri, "parent" );
      repos.appendChild( repos_parent );
      repos_parent.setAttribute( "uuid", String.valueOf(assignment.getParent().getUuid()) );
    }

    final Element repos_root = aDoc.createElementNS( nsUri, "root" );
    repos.appendChild( repos_root );
    if ( assignment.getRoot() != null ){
      repos_root.setAttribute( "uuid", String.valueOf(assignment.getRoot().getUuid()) );
    }

    final String chgDesc = assignment.getChangeDescription();
    if ( chgDesc != null ){
      final Element history = aDoc.createElementNS( nsUri, "history" );
      history.appendChild( aDoc.createTextNode(chgDesc) );
      repos.appendChild( history );
    }

    final Element acl = aDoc.createElementNS( nsUri, "acl" );
    repos.appendChild( acl );

    final Collection<AssignmentCustomProperty> custProps = assignment.getAsgnCustPropCollection();
    if ( custProps != null && custProps.size() > 0){
      final Element cust = aDoc.createElementNS( nsUri, "custom-properties" );
      root.appendChild( cust );

      for ( AssignmentCustomProperty cp : custProps ){
        final Element property = aDoc.createElementNS( nsUri, "property" );
        property.setAttribute( "name", cp.getName() );
        property.setAttribute( "value", cp.getPropValue() );
        cust.appendChild( property );
      }
    }

    return asBytes(aDoc, docBld);
  }


  /**
   * Extract byte array.
   * 
   * @return a byte array representation of the assignment metadata.
   */
  public byte[] build() {
    return content;
  }
}
